// author: Maulin Shah, M.D. maulinpshah at gmail dot com
dojo.provide("doh.TestCase");
dojo.require("doh.runner");

dojo.declare("doh.TestCase", null, {
	constructor: function(/* String */testName, /* function */ test, /* Object? */ scaffold){
	 // summary: A TestCase that can be registered and run with D.O.H.
     // description: While not mandatory, using TestCase to write your D.O.H. unit tests can simplify and speed unit
     // 				test development. TestCase also provides features that make your tests more readable
     //  			and makes writing the "scaffolding" for your cases easier.
     // example: Simple test case
     // |Notice how you have access to your scaffold, and the assert// methods as local variables in your test method.
     // |	doh.register("myTestSuite", [
     // |		new doh.TestCase("myTest", function() {
     // |				assertEqual("oldValue", someProperty, "someProperty should have been initialized!");
     // |	
     // | 				someProperty = "newValue";
     // |
     // |				assertEqual("newValue", someProperty, "someProperty should have been changed!");
     // |
     // |				assertTrue(someSetUpValue, "someSetUpValue should have been true");
     // |			}, {
     // |				setUp:function() {
     // |					this.someSetUpValue = true;
     // |				},
     // |				someProperty:"oldValue"
     // |			})
     // | ]);
     // example: Re-using your scaffolding
     // |The real power of TestCase comes when you start to resuse your scaffolding, as follows:
     // |	var scaffolding = {
     // |			setUp:function() {
     // |				this.someSetUpValue = true;
     // |			},
     // |			someProperty:"oldValue"
     // |	}
     // |	doh.register("myTestSuite", [
     // |		new doh.TestCase("myTest", function() {
     // |				assertEqual("oldValue", someProperty, "someProperty should have been initialized!");
     // |
     // | 				someProperty = "newValue";
     // |
     // |				assertEqual("newValue", someProperty, "someProperty should have been changed!");
     // |
     // |				assertTrue(someSetUpValue, "someSetUpValue should have been true");
     // |			}, scaffolding),
     // |		new doh.TestCase("myOtherTest", function() {
     // |				//notice how someProperty is back to "oldValue" again
     // |				assertEqual("oldValue", someProperty, "someProperty should have been initialized!");
     // |
     // | 				someProperty = "anotherNewValue";
     // |
     // |				assertEqual("newValue", someProperty, "someProperty should have been changed!");
     // |
     // |				assertTrue(someSetUpValue, "someSetUpValue should have been true");
     // |			}, scaffolding)
     // | ]);
     // example:Subclassing the test case to make a common scaffolding
	 // |	dojo.declare("MyTestCase", doh.TestCase, {
     // |			setUp:function() {
     // |				this.someSetUpValue = true;
     // |			},
     // |			someProperty:"oldValue"
	 // |	}
     // |	doh.register("myTestSuite", [
     // |		new MyTestCase("myTest", function() {
     // |				assertEqual("oldValue", someProperty, "someProperty should have been initialized!");
     // |
     // | 				someProperty = "newValue";
     // |
     // |				assertEqual("newValue", someProperty, "someProperty should have been changed!");
     // |
     // |				assertTrue(someSetUpValue, "someSetUpValue should have been true");
     // |			}),
     // |		new MyTestCase("myOtherTest", function() {
     // |				//notice how someProperty is back to "oldValue" again
     // |				assertEqual("oldValue", someProperty, "someProperty should have been initialized!");
     // |
     // | 				someProperty = "anotherNewValue";
     // |
     // |				assertEqual("newValue", someProperty, "someProperty should have been changed!");
     // |
     // |				assertTrue(someSetUpValue, "someSetUpValue should have been true");
     // |			})
     // | ]);	 
     // testName: String
     // 		The name of your test
     // test: Function
     // 		This is the function that is executed as your "test". You can access all of your scaffold's methods
     // 		and properties in this function as LOCAL variables. In addition you can call any of the assert// methods
     //      from within this function WITHOUT any prefix (no "this.assert//" needed, just "assert//). You can also use
     //      fail() as a local method.
     // scaffold: Object?
     // 		Properties of this object are injected into your test method as LOCAL variables. In addition, you
     // 		can provide a setUp and tearDown methods in this object that will be called before and after your test.
     // 	    (An alternative to providing the same scaffold to each test would be to create your own subclass of TestCase
     //      and then all the properties and methods that you declare in the subclass would be available to your
     //      test method).
     //
        this.name = testName;
        this.testMethod = test;
        if (scaffold) {
            dojo.mixin(this, scaffold);
        }
        
    },
    setUp: function(){
        //summary:run before each test reun
    },
    tearDown: function(){
        //run after each test run
    },
    
    fail: function(/* String */message){
        console.error(message || "fail()");
        throw new doh._AssertFailure(message || "fail()");
    },
    
    assertContainsKey: function(/* Object | Array | String to eval */src, /* String */ key, /* String? */ message){
        var srcName = src;
		try {
			if (dojo.isString(src)) {
				src = eval(src);
			}
            doh.assertTrue(key in src);
        } 
        catch (e) {
            console.error(message || srcName + " does not contain " + key, src, key);
            throw (e);
        }
    },
    assertTrue: function(/* Boolean | String to eval */condition, /* String? */ message){
         var conditionExpression = condition;
        try {
            if (dojo.isString(condition)) {
                condition = eval(condition);
            }
            doh.assertTrue(condition);
        } 
        catch (e) {
            console.error(message || "assertTrue failed for " + conditionExpression, condition);
            throw (e);
        }
    },
    assertFalse: function(/* Boolean | String to eval */condition, /* String? */ message){
         var conditionExpression = condition;
        try {
            if (dojo.isString(condition)) {
                condition = eval(condition);
            }
            doh.assertFalse(condition);
        } 
        catch (e) {
            console.error(message || "assertFalse failed for " +  conditionExpression, condition);
            throw (e);
        }
    },
    assertNull: function(/* Any | String to eval */condition, /* String? */ message){
        try {
            doh.assertTrue(condition === null);
        } 
        catch (e) {
            console.error(message || "assertNull failed", condition);
            throw (e);
        }
    },
    assertNotNull: function(/* Any | String to eval */condition, /* String? */ message){
        var conditionExpression = condition;
		try {
			if (dojo.isString(condition)) {
				condition = eval(condition);
			}
            doh.assertFalse(condition === null);
        } 
        catch (e) {
            console.error(message || "assertNotNull failed for " + conditionExpression, condition);
            throw (e);
        }
    },
    assertUndefined: function(/* Any | String to eval */condition, /* String? */ message){
        var conditionExpression = condition;
        try {
            if (dojo.isString(condition)) {
                condition = eval(condition);
            }
            doh.assertTrue(typeof condition === 'undefined');
        } 
        catch (e) {
            console.error(message || "assertUndefined failed for " + conditionExpression, condition);
            throw (e);
        }
    },
    assertDefined: function(/* Any | String to eval */condition, /* String? */ message){
         var conditionExpression = condition;
        try {
            if (dojo.isString(condition)) {
                condition = eval(condition);
            }
            doh.assertFalse(typeof condition === 'undefined');
        } 
        catch (e) {
            console.error(message || "assertDefined failed for " + conditionExpression, condition);
            throw (e);
        }
    },
    
    assertEqual: function(/* Any */left, /* Any */ right, /* String? */ message){
        try {
            doh.assertEqual(left, right);
        } 
        catch (e) {
            if (!message) {
                message = "left should equal right, but does not"
            }
            console.error(message, left, right);
            throw (e);
        }
    },
    assertInstanceOf: function(/* Any | String to eval */entity, /* Class | String to eval */ entityClass, /* String? */ message){
        var entityName = entity;
		var entityClassName = entityClass;
		try {
			if (dojo.isString(entity)) {
				entity = eval(entityName);
			}
			if (dojo.isString(entityClass)) {
                entityClass = eval(entityClass);
            }
            doh.assertTrue(entity instanceof entityClass);
        } 
        catch (e) {
            console.error(message || entityName + " should be and insanceof " + entityClassName, entity, entityClass);
            throw (e);
        }
    },
    
    /**
     * This is the meat of TestCase. It runs the passed method, injecting all properties
     * of this as local variables/methods into the test.
     * @param {Function} method: the method to run with the injected values
     * @param {Array} args: arguments array to pass to the test method (apply)
     */
    _injectAndRun: function(/* Function */method, /* Array */ args){
        for (var prop in this) {
            var declaration = "";
            if (dojo.isFunction(this[prop])) {
                declaration = "var " + prop + " = dojo.hitch(this, \"" + prop + "\");";
            }
            else {
                declaration = "var " + prop + " = this." + prop + ";";
            }
            eval(declaration);
        }
        
        method = "" + method;
        
        var functionAsString = "(" + method + ").apply(this, args);";
        
        return eval(functionAsString);
        
    },
    runTest: function(){
        this._injectAndRun(this.testMethod);
    },
    toString: function(){
        return this.declaredClass + ": " + this.name;
    }
});


dojo.declare("doh.AsyncTestCase", doh.TestCase, {
    constructor: function(/* String */testName, /* Function */ asyncTest, /* Function */ assertionsCallback, /* Object? */ scaffold){
     // summary: A TestCase to simplify asynchronous testing.
     // description: An AsyncTestCase give you all the same benefits as TestCase, but allows you to execute the test
     // 			    asynchronously.
     // example: Using with a Deferred asynchronous process
     // |	new doh.AsyncTestCase("deferredTest", function() {
     // |			return doSomethingDeferred();
     // |		}, function(someArg1, someArg2) {
     // |				assertEquals(someArg1, someArg2);
     // |			});
     // example: Using without Deferred
     // |	new doh.AsyncTestCase("aynchronousTest", function() {
     // |			doSomethingThatHasACallback(callback); //'callback' is a magic function
     // |		}, function(someArg1, someArg2) {
     // |				assertEquals(someArg1, someArg2);
     // |		}); 
     // testName: String
     // asyncTest: Function
	 //			 a method that either returns a dojo.Deferred, returns a doh.Deferred, or sets the "magic" variable
     // 		"callback" to be executed when your asynchronous processing is completed and you are ready to test the results.
     // 		As in TestCase you have full access to your scaffold and assert// methods as local variables.
     // assertionsCallback: Function
	 //			 this will be called automatically when your asynchronous method is completed, with
     // 		any arguments that your callback would normally have received from your asynchronous method. As in TestCase
     // 		you have full access to your scaffold and assert// methods as local variables.
     // scaffold: Object
	 //			See TestCase
        this.name = testName;
        this.asyncTest = asyncTest;
        this.assertionsCallback = assertionsCallback;
        if (scaffold) {
            dojo.mixin(this, scaffold);
        }
    },
    _runAssertionsCallback: function(){
        return this._injectAndRun(this.assertionsCallback, arguments);
    },
    runTest: function(){
        var d = new doh.Deferred();
        this.dohDeferred = d;
        this.callback = function(){
            try {
                this._runAssertionsCallback.apply(this, arguments);
                d.callback(true);
            } 
            catch (e) {
                d.errback(e);
            }
        }
        var actualDeferred = this._injectAndRun(this.asyncTest);
        if (actualDeferred) {
            actualDeferred.addBoth(d.getTestCallback(dojo.hitch(this, "_runAssertionsCallback")));
        }
        else {
			//see if the word "callback" is somewhere in asyncTest, if not, throw a warning
			if (("" + this.asyncTest).indexOf("callback") < 0) {
				console.info("no deferred returned from test. either return a deferred, or be sure you have assigned 'callback' to be called when your aynchronous method returns.");
			}
        }
        return d;
    }
});
